-
Notifications
You must be signed in to change notification settings - Fork 1k
Add settings
to Model base class
#2136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
PR Change SummaryEnhanced the Model base class by introducing a settings field and updating the documentation to reflect the new settings hierarchy.
Modified Files
How can I customize these reviews?Check out the Hyperlint AI Reviewer docs for more information on how to customize the review. If you just want to ignore it on this PR, you can add the Note specifically for link checks, we only check the first 30 links in a file and we cache the results for several hours (for instance, if you just added a page, you might experience this). Our recommendation is to add |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@svilupp Thanks for working on this!
# Merge model settings from any Model | ||
if isinstance(model_used, InstrumentedModel): | ||
# For InstrumentedModel, get settings from the wrapped model | ||
wrapped_model_settings = getattr(model_used.wrapped, 'settings', None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we define settings
on WrapperModel
(which InstrumentedModel
inherits from) to return self.wrapped.settings
, so we don't need to do this special-casing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That may require us to rename the field on Model
to _settings
, so that we can define settings
as a property that can be overridden on WrapperModel
.
model_settings = merge_model_settings(wrapped_model_settings, model_settings) | ||
else: | ||
# For regular models, use their settings directly | ||
current_settings = getattr(model_used, 'settings', None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be model_used.settings
?
|
||
def __init__( | ||
self, | ||
wrapped: Model | KnownModelName, | ||
options: InstrumentationSettings | None = None, | ||
) -> None: | ||
super().__init__(wrapped) | ||
self.settings = options or InstrumentationSettings() | ||
# Store instrumentation settings separately from model settings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this comment or the next one, with the different field names it's clear enough what's happening
# Store instrumentation settings separately from model settings | ||
self.instrumentation_settings = options or InstrumentationSettings() | ||
# Initialize base Model with no settings to avoid storing InstrumentationSettings there | ||
Model.__init__(self, settings=None) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we make _settings
a field on Model
with default value None
(like _profile
currently is), we can skip this init.
@@ -42,6 +42,10 @@ class MCPSamplingModel(Model): | |||
[`ModelSettings`][pydantic_ai.settings.ModelSettings], so this value is used as fallback. | |||
""" | |||
|
|||
def __post_init__(self): | |||
"""Initialize the base Model class.""" | |||
super().__init__() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can likely also remove this if _settings: ModelSettings | None = None
becomes a Model field.
# Run with additional settings | ||
run_settings = ModelSettings(temperature=0.8, seed=42) | ||
|
||
# This should work without errors and properly merge the settings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we verify it ends up with the correct settings?
def test_gemini_model_settings(): | ||
"""Test that GeminiModel can be initialized with settings.""" | ||
if not gemini_available or GeminiModel is None: # pragma: no cover | ||
return # Skip if dependencies not available |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use pytest.skip
here, there are some examples in the codebase
return ModelResponse(parts=[TextPart('test')]) | ||
|
||
# Replace the instrumented model's wrapped model with a function model for testing | ||
instrumented_model.wrapped = FunctionModel(test_response, settings=base_model_settings) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's create a new InstrumentedModel
instead of reusing the existing one, as WrapperModel
caches some properties so changing wrapped
in place is not handled correctly across the board.
2. **Agent-level defaults** - Set during [`Agent`][pydantic_ai.agent.Agent] initialization via the `model_settings` argument. These override model defaults. | ||
3. **Run-time overrides** - Passed to `run{_sync,_stream}` functions via the `model_settings` argument. These have the highest priority and override both agent and model defaults. | ||
|
||
**Settings Precedence**: Run-time > Agent > Model |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can drop this line as the list already explicitly states the priority.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it may be useful to write here that the settings are merged, not replaced.
@@ -124,6 +124,39 @@ The `ModelResponse` message above indicates in the `model_name` field that the o | |||
!!! note | |||
Each model's options should be configured individually. For example, `base_url`, `api_key`, and custom clients should be set on each model itself, not on the `FallbackModel`. | |||
|
|||
### Per-Model Settings | |||
|
|||
You can configure different `ModelSettings` for each model in a fallback chain by passing the `settings` parameter when creating each model. This is particularly useful when different providers have different optimal configurations: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can configure different `ModelSettings` for each model in a fallback chain by passing the `settings` parameter when creating each model. This is particularly useful when different providers have different optimal configurations: | |
You can configure different [`ModelSettings`][pydantic_ai.settings.ModelSettings] for each model in a fallback chain by passing the `settings` parameter when creating each model. This is particularly useful when different providers have different optimal configurations: |
Fixes #2119
The implementation follows the suggestion by @DouweM
This PR:
settings
field in the Model classmodel settings < agent model settings < run-specific model settings
# Issues Encountered
I dediced on the following behaviour -- any thoughts?
settings
, so I had to change the field definition to beself.settings: ModelSettings | InstrumentationSettings | None = settings
settings
field for API consistencyNext Steps
agent.run()
) to be a separate utility with its own tests?